﻿#region using Directives

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Threading;

#endregion

namespace InstaMe.Controls
{
    public class CustomObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
    {
        #region Fields

        private const string CountString = "Count";
        private const string IndexerName = "Item[]";

        private readonly SimpleMonitor _monitor = new SimpleMonitor();

        public string Key
        {
            get;
            set;
        }

        #endregion

        #region Properties

//        protected IList<T> InnerCollection { get; private set; }

        #endregion

        #region Constructors

        public CustomObservableCollection()
            :base()
        {            
//            InnerCollection = new List<T>();
        }

        public CustomObservableCollection(IList<T> list)
            :base((list != null) ? new List<T>(list.Count) : list)
        {
            if (list == null)
            {
                throw new ArgumentNullException("innerCollection");
            }

//            InnerCollection = innerCollection;
        }

        #endregion

        #region Implementation

        #region Implementation of INotifyCollectionChanged

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null)
            {
                using (BlockReentrancy())
                {
                    CollectionChanged(this, e);
                }
            }
        }

        /// <summary>
        /// Used for Add or Remove Item
        /// </summary>
        /// <param name="action">Add or Remove</param>
        /// <param name="item"></param>
        /// <param name="index"></param>
        private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index)
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
        }
        /// <summary>
        /// Used for Add or Reset aList Item
        /// </summary>
        /// <param name="action"></param>
        /// <param name="listItem"></param>
        /// <param name="startIndex"></param>
        private void OnCollectionChanged(NotifyCollectionChangedAction action, IList listItem, int startIndex)
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, listItem, startIndex));
        }

        /// <summary>
        /// Used for Move Item
        /// </summary>
        /// <param name="action">Move</param>
        /// <param name="item"></param>
        /// <param name="newIndex"></param>
        /// <param name="oldIndex"></param>
        private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int newIndex, int oldIndex)
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, newIndex, oldIndex));
        }

        /// <summary>
        /// Used For Replace
        /// </summary>
        /// <param name="action">Replace</param>
        /// <param name="aNewItem"></param>
        /// <param name="aOldItem"></param>
        /// <param name="aIndex"></param>
        private void OnCollectionChanged(NotifyCollectionChangedAction action, object aNewItem, object aOldItem, int aIndex)
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, aNewItem, aOldItem, aIndex));
        }

        private void OnCollectionReset()
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        #endregion

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }

        private void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region Implementation of IEnumerable

        //public IEnumerator<T> GetEnumerator()
        //{
        //    return InnerCollection.GetEnumerator();
        //}

        //IEnumerator IEnumerable.GetEnumerator()
        //{
        //    return GetEnumerator();
        //}

        #endregion

        #region Implementation of ICollection<T>

        private void CopyFrom(IEnumerable<T> collection)
        {
//            ICollection<T> items = InnerCollection;
            ICollection<T> items = Items;
            if (collection != null && items != null)
            {
                using (IEnumerator<T> enumerator = collection.GetEnumerator())
                {
                    while (enumerator.MoveNext())
                    {
                        items.Add(enumerator.Current);
                    }
                }
            }
        }

        private void CopyFromToTop(IList<T> collection)
        {
            //            ICollection<T> items = InnerCollection;
            IList<T> items = Items;
            if (collection != null && items != null)
            {
                for (int iter = collection.Count - 1; iter >= 0; iter--)
                {
                    items.Insert(0, collection[iter]);
                }
            }
        }  

        public void ManualAdd(T item)
        {
            CheckReentrancy();
//            InnerCollection.Add(item);
            int iChangedIndex = base.Count;
            base.Add(item);
            OnPropertyChanged(CountString);
            OnPropertyChanged(IndexerName);
//            OnCollectionChanged(NotifyCollectionChangedAction.Add, item, InnerCollection.Count);
            OnCollectionChanged(NotifyCollectionChangedAction.Add, item, iChangedIndex);
        }

        public void ManualAdd(List<T> aListItem)
        {
            if (aListItem.Count > 0)
            {
                CheckReentrancy();
                int iStartIndex = base.Count;
                CopyFrom(aListItem);
                OnPropertyChanged(CountString);
                OnPropertyChanged(IndexerName);
                //            OnCollectionChanged(NotifyCollectionChangedAction.Add, aListItem, InnerCollection.Count);
                OnCollectionChanged(NotifyCollectionChangedAction.Add, aListItem, iStartIndex);
            }
        }

        public void ManualAddToTop(T item)
        {
            CheckReentrancy();
            base.Insert(0, item);
            OnPropertyChanged(CountString);
            OnPropertyChanged(IndexerName);            
            OnCollectionChanged(NotifyCollectionChangedAction.Add, item, 0);
        }

        public void ManualAddToTop(List<T> aListItem)
        {
            if (aListItem.Count > 0)
            {
                CheckReentrancy();            
                CopyFromToTop(aListItem);
                OnPropertyChanged(CountString);
                OnPropertyChanged(IndexerName);
    //            OnCollectionChanged(NotifyCollectionChangedAction.Add, aListItem, InnerCollection.Count);
                OnCollectionChanged(NotifyCollectionChangedAction.Add, aListItem, 0);
            }
        }

        public void ManualInsert(int aIndex, T item)
        {
            CheckReentrancy();
//            InnerCollection.Insert(aIndex, item);
            base.Insert(aIndex, item);
            OnPropertyChanged(CountString);
            OnPropertyChanged(IndexerName);
            OnCollectionChanged(NotifyCollectionChangedAction.Add, item, aIndex);
        }

        public void ManualReplace(int aIndex, T item)
        {
            CheckReentrancy();
//            InnerCollection.RemoveAt(aIndex);
//            InnerCollection.Insert(aIndex, item);
            T iOldItem = this[aIndex];
            base.RemoveAt(aIndex);
            base.Insert(aIndex, item);
            OnPropertyChanged(IndexerName);
            OnCollectionChanged(NotifyCollectionChangedAction.Replace, item, iOldItem, aIndex);
        }

        public void ManualMove(int aOldIndex, int aNewIndex)
        {
            CheckReentrancy();
//            T item = InnerCollection[aOldIndex];
//            InnerCollection.RemoveAt(aOldIndex);
//            InnerCollection.Insert(aNewIndex, item);
            T item = this[aOldIndex];
            base.RemoveAt(aOldIndex);
            base.Insert(aNewIndex, item);
            OnPropertyChanged(IndexerName);
            OnCollectionChanged(NotifyCollectionChangedAction.Move, item, aNewIndex, aOldIndex);
        }

        public bool ManualRemove(T item, bool safeRemove = false)
        {
            CheckReentrancy();
//            bool result = InnerCollection.Remove(item);
            int iIndex = IndexOf(item);
            if (iIndex >= 0 && iIndex < base.Count)
            {             
                base.RemoveAt(iIndex);
                OnPropertyChanged(CountString);
                OnPropertyChanged(IndexerName);
                if (safeRemove) {
                    OnCollectionReset();
                } else {
                    OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, iIndex);
                }
                return true;
            }
            else
            {
                return false;
            }                        
        }

        public void ManualRemoveAt(int aIndex, bool safeRemove = false)
        {
            CheckReentrancy();
//            T item = InnerCollection[aIndex];
//            InnerCollection.RemoveAt(aIndex);
            T item = this[aIndex];
            base.RemoveAt(aIndex);
            OnPropertyChanged(CountString);
            OnPropertyChanged(IndexerName);

            if (safeRemove) {
                OnCollectionReset();
            } else {
                OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, aIndex);
            }
        }

        public void ManualClear()
        {
            CheckReentrancy();
//            InnerCollection.Clear();
            base.Clear();
            OnPropertyChanged(CountString);
            OnPropertyChanged(IndexerName);
            OnCollectionReset();
        }

//        public bool Contains(T item)
//        {
////            return InnerCollection.Contains(item);
//            return base.Contains(item);
//        }

//        public void CopyTo(T[] array, int arrayIndex)
//        {
////            InnerCollection.CopyTo(array, arrayIndex);
//            base.CopyTo(array, arrayIndex);
//        }

        //public int Count
        //{
        //    get { return InnerCollection.Count; }
        //}

        //public bool IsReadOnly
        //{
        //    get { return InnerCollection.IsReadOnly; }
        //}

        #endregion

        #region Simple Monitor

        protected IDisposable BlockReentrancy()
        {
            this._monitor.Enter();
            return this._monitor;
        }

        protected void CheckReentrancy()
        {
            if ((this._monitor.Busy && (CollectionChanged != null)) && (CollectionChanged.GetInvocationList().Length > 1))
            {
                throw new InvalidOperationException("Collection Reentrancy Not Allowed");
            }
        }

//        [Serializable]
        private class SimpleMonitor : IDisposable
        {
            private int _busyCount;

            public bool Busy
            {
                get { return this._busyCount > 0; }
            }

            public void Enter()
            {
                this._busyCount++;
            }

            #region Implementation of IDisposable

            public void Dispose()
            {
                this._busyCount--;
            }

            #endregion
        }

        #endregion

        #endregion
    }
}